package com.sh.rbac.core.common.aspect;

import cn.hutool.json.JSONUtil;
import com.sh.rbac.core.common.annotation.SysLog;
import com.sh.rbac.core.common.enums.OperationStatusEnum;
import com.sh.rbac.core.common.util.IpAddressUtils;
import com.sh.rbac.core.common.util.WebUtils;
import com.sh.rbac.core.security.util.SecurityHolderUtils;
import com.sh.rbac.domain.entity.OperationLog;
import com.sh.rbac.domain.entity.User;
import com.sh.rbac.service.OperationLogService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * 操作日志切面
 *
 * @author wb
 * @since 2024-07-07
 */

@Slf4j
@Aspect
@Component
public class OperationAspect {


    @Autowired
    private OperationLogService operationLogService;

    final ThreadLocal<OperationLog> logThreadLocal = new ThreadLocal<>();

    private long startTime;


    // 前置通知
    @Before("@annotation(sysLog)")
    public void beforeLogger(JoinPoint joinPoint, SysLog sysLog) {
        startTime = System.currentTimeMillis();

        HttpServletRequest request = WebUtils.getRequest();

        // 获取方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();

        // 封装日志
        OperationLog operationLog = new OperationLog();

        User loginUser = SecurityHolderUtils.getLoginUser();

        operationLog.setUserId(loginUser.getId());
        operationLog.setUsername(SecurityHolderUtils.getUsername());
        operationLog.setDeptId(loginUser.getDeptId());
        operationLog.setDeptName(loginUser.getDeptName());

        operationLog.setOperationModule(sysLog.module());
        operationLog.setOperationType(sysLog.type().getCode());
        operationLog.setOperationMethod(method.getDeclaringClass().getName() + "." + method.getName());
        operationLog.setOperationParams(JSONUtil.toJsonStr(joinPoint.getArgs()));
        operationLog.setOperationUrl(request.getRequestURI());
        operationLog.setRequestMethod(request.getMethod());
        operationLog.setOperationIp(IpAddressUtils.getIpAddress(request));
        operationLog.setOperationTime(LocalDateTime.now());

        // 存入 ThreadLocal
        logThreadLocal.set(operationLog);

    }

    // 后置通知
    @AfterReturning(value = "@annotation(sysLog)", returning = "result")
    public void afterLogger(SysLog sysLog, Object result) {
        long endTime = System.currentTimeMillis();
        // 获取 ThreadLocal 中的操作日志对象
        OperationLog operationLog = logThreadLocal.get();

        if (operationLog != null) {
            // 设置操作状态
            operationLog.setOperationStatus(OperationStatusEnum.SUCCESS.getCode());

            if (result != null) {
                // 设置操作结果
                operationLog.setOperationResult(JSONUtil.toJsonStr(result));
            }

            // 设置请求耗时
            operationLog.setRequestTime((int) (endTime - startTime));

            // 保存操作日志
            operationLogService.save(operationLog);

            // 从 ThreadLocal 中删除操作日志对象
            logThreadLocal.remove();
        }
    }

    // 异常通知
    @AfterThrowing(value = "@annotation(sysLog)", throwing = "throwable")
    public void throwingLogger(SysLog sysLog, Throwable throwable) {
        // 保存操作日志
        OperationLog operationLog = logThreadLocal.get();
        if (operationLog != null) {
            operationLog.setOperationStatus(OperationStatusEnum.FAILURE.getCode());
            String throwableStr = throwable.toString();
            if (throwableStr.contains(":")) {
                throwableStr = throwableStr.substring(throwableStr.indexOf(":") + 1);
            }
            operationLog.setErrorMsg(throwableStr);

            // 调用具体的 service 保存到数据库中
            operationLogService.save(operationLog);

            // 移除本地线程数据
            logThreadLocal.remove();
        }

    }

}
